package main

import (
	"context"
	"fmt"
	"net/http"
	"sync"
	"time"

	"gollect/monitor/client"
	monproto "gollect/proto"
)

// WebMonitor represents the web-enabled monitoring service
type WebMonitor struct {
	webServer *WebServer
	trodes    map[string]*TrodeMonitor
	ctx       context.Context
	cancel    context.CancelFunc
	wg        sync.WaitGroup
	interval  time.Duration
	deployed  map[string]*Deployer
}

// TrodeMonitor represents a single trode monitoring instance
type TrodeMonitor struct {
	address   string
	interval  time.Duration
	client    *client.MonClient
	webServer *WebServer
	ctx       context.Context
	wg        *sync.WaitGroup
	done      chan struct{}
}

// NewWebMonitor creates a new web-enabled monitoring service
func NewWebMonitor(webPort int, interval time.Duration) (*WebMonitor, error) {
	ctx, cancel := context.WithCancel(context.Background())

	// Create web server with SQLite database in the data directory
	webServer, err := NewWebServer(webPort, interval, "data/monitor.db")
	if err != nil {
		cancel()
		return nil, fmt.Errorf("failed to create web server: %w", err)
	}

	return &WebMonitor{
		webServer: webServer,
		trodes:    make(map[string]*TrodeMonitor),
		ctx:       ctx,
		cancel:    cancel,
		interval:  interval,
		deployed:  make(map[string]*Deployer),
	}, nil
}

// AddTrode adds a trode to monitor
func (wm *WebMonitor) AddTrode(address string, interval time.Duration) error {
	// Create gRPC client for the trode
	mc, err := client.NewMonClient(address)
	if err != nil {
		return fmt.Errorf("failed to create monitor client for %s: %w", address, err)
	}

	trode := &TrodeMonitor{
		address:   address,
		interval:  interval,
		client:    mc,
		webServer: wm.webServer,
		ctx:       wm.ctx,
		wg:        &wm.wg,
		done:      make(chan struct{}),
	}

	wm.trodes[address] = trode
	wm.wg.Add(1)

	// Start monitoring this trode
	go trode.monitor()

	LogVerbose("Added trode: %s (interval: %v)", address, interval)
	return nil
}

// Start starts the web monitor
func (wm *WebMonitor) Start() error {
	// Start server in a goroutine
	go func() {
		if err := wm.webServer.server.ListenAndServe(); err != nil && err != http.ErrServerClosed {
			LogError("Server error: %v", err)
		}
	}()

	LogInfo("Web monitor started on port %s", wm.webServer.server.Addr)
	return nil
}

// Shutdown gracefully shuts down the web monitor
func (wm *WebMonitor) Shutdown() error {
	wm.cancel()

	// Give outstanding operations 5 seconds to complete
	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancel()

	if err := wm.webServer.server.Shutdown(ctx); err != nil {
		return fmt.Errorf("server shutdown error: %w", err)
	}

	// Wait for all trode monitors to finish
	wm.wg.Wait()

	return nil
}

// monitor monitors a single trode
func (pm *TrodeMonitor) monitor() {
	defer func() {
		pm.wg.Done()
		close(pm.done)
	}()
	defer pm.client.Close()

	ticker := time.NewTicker(pm.interval)
	defer ticker.Stop()

	LogVerbose("[%s] Started monitoring", pm.address)

	for {
		select {
		case <-ticker.C:
			// Create request context with timeout
			reqCtx, reqCancel := context.WithTimeout(pm.ctx, 5*time.Second)
			data, err := pm.client.FetchMonitoringData(reqCtx, &monproto.MonitoringRequest{})
			reqCancel()

			if err != nil {
				LogError("[%s] Error fetching monitoring data: %v", pm.address, err)
				// Update web server with error status
				pm.webServer.UpdateTrodeStatus(pm.address, false, nil, err)
				continue
			}

			// Debug log for timestamp
			LogVerbose("[%s] Envelope Timestamp: %d (%s)", pm.address, data.Timestamp, time.Unix(0, data.Timestamp).Format("2006-01-02 15:04:05"))

			// Update web server with successful data
			pm.webServer.UpdateTrodeStatus(pm.address, true, data, nil)

			// Log the data (optional, for debugging)
			pm.logMonitoringData(data)

		case <-pm.ctx.Done():
			LogVerbose("[%s] Stopping monitoring", pm.address)
			return
		}
	}
}

// logMonitoringData logs monitoring data (for debugging)
func (pm *TrodeMonitor) logMonitoringData(data *monproto.MonitoringEnvelope) {
	LogVerbose("[%s] Received monitoring data (timestamp: %v)",
		pm.address, time.Unix(0, data.Timestamp))

	switch payload := data.Payload.(type) {
	case *monproto.MonitoringEnvelope_Cpu:
		LogVerbose("[%s] CPU Load: %.2f%%", pm.address, payload.Cpu.TotalLoad*100)
	case *monproto.MonitoringEnvelope_Memory:
		LogVerbose("[%s] Memory Used: %.2f%%", pm.address, payload.Memory.UsedPercent*100)
	case *monproto.MonitoringEnvelope_Disk:
		pm.logDetailedDiskInfo(payload.Disk)
	case *monproto.MonitoringEnvelope_Network:
		LogVerbose("[%s] Network Sent: %d MB, Received: %d MB",
			pm.address, payload.Network.TotalBytesSent/1024/1024, payload.Network.TotalBytesRecv/1024/1024)
	case *monproto.MonitoringEnvelope_Processes:
		LogVerbose("[%s] Process Data: %d top processes", pm.address, len(payload.Processes.TopProcesses))
		if len(payload.Processes.TopProcesses) > 0 {
			top := payload.Processes.TopProcesses[0]
			LogVerbose("[%s] Top Process: %s (PID: %d, CPU: %.2f%%, Memory: %.2f%%)",
				pm.address, top.Name, top.RootPid, top.Cpu*100, top.Mem*100)
		}
	case *monproto.MonitoringEnvelope_Users:
		LogVerbose("[%s] User Data: %d top users", pm.address, len(payload.Users.TopUsers))
		if len(payload.Users.TopUsers) > 0 {
			top := payload.Users.TopUsers[0]
			LogVerbose("[%s] Top User: %s (CPU: %.2f%%, Memory: %.2f%%)",
				pm.address, top.User, top.Cpu*100, top.Mem*100)
		}
	}
}

// logDetailedDiskInfo logs detailed disk usage information for all partitions
func (pm *TrodeMonitor) logDetailedDiskInfo(diskInfo *monproto.DiskInfo) {
	if len(diskInfo.Partitions) == 0 {
		LogVerbose("[%s] No disk partitions found", pm.address)
		return
	}

	LogVerbose("[%s] === DISK USAGE INFORMATION ===", pm.address)
	LogVerbose("[%s] Found %d disk partition(s)", pm.address, len(diskInfo.Partitions))

	// Track which partition will be used for the trode's disk metric
	rootPartitionFound := false
	selectedPartition := -1

	for i, partition := range diskInfo.Partitions {
		// Format bytes to human readable format
		totalStr := pm.formatBytes(partition.Total)
		usedStr := pm.formatBytes(partition.Used)
		freeStr := pm.formatBytes(partition.Free)
		usagePercent := partition.UsedPercent * 100

		// Check if this is the root partition
		isRoot := partition.Mountpoint == "/"
		if isRoot {
			rootPartitionFound = true
			selectedPartition = i
		} else if selectedPartition == -1 && i == 0 {
			// First partition will be used as fallback
			selectedPartition = i
		}

		LogVerbose("[%s] Partition %d:", pm.address, i+1)
		LogVerbose("[%s]   Mountpoint: %s", pm.address, partition.Mountpoint)
		LogVerbose("[%s]   Total Space: %s", pm.address, totalStr)
		LogVerbose("[%s]   Used Space: %s (%.1f%%)", pm.address, usedStr, usagePercent)
		LogVerbose("[%s]   Free Space: %s", pm.address, freeStr)

		// Create visual progress bar
		barLength := 20
		usedBars := int(float64(barLength) * float64(partition.UsedPercent))
		bar := ""
		for j := 0; j < barLength; j++ {
			if j < usedBars {
				bar += "█"
			} else {
				bar += "░"
			}
		}
		LogVerbose("[%s]   Usage: [%s] %.1f%%", pm.address, bar, usagePercent)

		// Mark the selected partition
		if i == selectedPartition {
			if rootPartitionFound && partition.Mountpoint == "/" {
				LogVerbose("[%s]   → SELECTED (root partition)", pm.address)
			} else {
				LogVerbose("[%s]   → SELECTED (fallback to first partition)", pm.address)
			}
		}
		LogVerbose("[%s]", pm.address) // Empty line for readability
	}

	// Summary of selection
	if rootPartitionFound {
		LogVerbose("[%s] Using root partition (/) for trode disk metric", pm.address)
	} else {
		LogVerbose("[%s] Root partition (/) not found, using first partition as fallback", pm.address)
	}

	// Log disk I/O information if available
	if diskInfo.TotalReadBytes > 0 || diskInfo.TotalWriteBytes > 0 {
		LogVerbose("[%s] Disk I/O:", pm.address)
		LogVerbose("[%s]   Total Read: %s", pm.address, pm.formatBytes(diskInfo.TotalReadBytes))
		LogVerbose("[%s]   Total Write: %s", pm.address, pm.formatBytes(diskInfo.TotalWriteBytes))
	}
}

// formatBytes converts bytes to human readable format
func (pm *TrodeMonitor) formatBytes(bytes uint64) string {
	const unit = 1024
	if bytes < unit {
		return fmt.Sprintf("%d B", bytes)
	}
	div, exp := int64(unit), 0
	for n := bytes / unit; n >= unit; n /= unit {
		div *= unit
		exp++
	}
	return fmt.Sprintf("%.1f %cB", float64(bytes)/float64(div), "KMGTPE"[exp])
}

// DeployedTrodeInfo holds information about a deployed trode
type DeployedTrodeInfo struct {
	Host      string `json:"host"`
	Port      int    `json:"port"`
	User      string `json:"user"`
	RemoteDir string `json:"remote_dir"`
	Running   bool   `json:"running"`
}

// DeployTrode deploys a new trode to a remote node
func (wm *WebMonitor) DeployTrode(config *DeploymentConfig) error {
	// Create deployer
	deployer, err := NewDeployer(config)
	if err != nil {
		return fmt.Errorf("failed to create deployer: %w", err)
	}

	// Deploy the trode
	result, err := deployer.DeployTrode(wm.ctx)
	if err != nil {
		deployer.Close()
		return fmt.Errorf("failed to deploy trode: %w", err)
	}
	
	if !result.Success {
		deployer.Close()
		return fmt.Errorf("deployment failed: %s", result.Error)
	}

	// Store deployer for later management
	trodeKey := fmt.Sprintf("%s:%d", config.SSHHost, config.TrodePort)
	wm.deployed[trodeKey] = deployer

	// Wait a moment for the trode to start up
	time.Sleep(3 * time.Second)

	// Add the deployed trode to monitoring
	trodeAddress := deployer.GetTrodeAddress()
	if err := wm.AddTrode(trodeAddress, wm.interval); err != nil {
		LogError("Failed to add deployed trode to monitoring: %v", err)
		// Don't return error here as deployment was successful
	}

	LogInfo("Successfully deployed and started monitoring trode at %s", trodeAddress)
	return nil
}

// RemoveDeployedTrode stops and removes a deployed trode
func (wm *WebMonitor) RemoveDeployedTrode(host string, port int) error {
	trodeKey := fmt.Sprintf("%s:%d", host, port)

	deployer, exists := wm.deployed[trodeKey]
	if !exists {
		return fmt.Errorf("no deployed trode found for %s", trodeKey)
	}

	// Stop the trode
	if err := deployer.StopTrode(); err != nil {
		LogError("Failed to stop deployed trode: %v", err)
	}

	// Remove from monitoring
	trodeAddress := deployer.GetTrodeAddress()
	if trode, exists := wm.trodes[trodeAddress]; exists {
		// Signal trode to stop
		select {
		case <-trode.done:
			// Already done
		default:
			// Will be cleaned up by the monitor goroutine
		}
	}

	// Close SSH connection
	deployer.Close()

	// Remove from tracking
	delete(wm.deployed, trodeKey)

	LogInfo("Successfully removed deployed trode at %s", trodeAddress)
	return nil
}

// GetDeployedTrodes returns information about deployed trodes
func (wm *WebMonitor) GetDeployedTrodes() map[string]*DeployedTrodeInfo {
	result := make(map[string]*DeployedTrodeInfo)

	for trodeKey, deployer := range wm.deployed {
		status, err := deployer.CheckTrodeStatus()
		if err != nil {
			LogError("Failed to check status for %s: %v", trodeKey, err)
			status = false
		}

		result[trodeKey] = &DeployedTrodeInfo{
			Host:      deployer.config.SSHHost,
			Port:      deployer.config.TrodePort,
			User:      deployer.config.SSHUser,
			RemoteDir: deployer.config.RemoteDir,
			Running:   status,
		}
	}

	return result
}
